package server;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import dictionary.interfaceData.*;

/**
 * Status: finished, but needs testing
 * 
 * @author mjp
 * @version 1.0
 * @created 06-Nov-2011 10:35:23 AM
 */
public class ServerMessager implements DataReceiver {

	private Server clueServer;

	HashMap<Integer, ObjectOutputStream> playerToSocketOutput = new HashMap<Integer, ObjectOutputStream>();
	List <Player> players = new ArrayList<Player>();
	// time alloted for receiving new users, in milliseconds
	private int MAX_TIME_TO_JOIN = Server.DEBUG?1000:(20*1000);
	
	int playerId = 0;
	long startTime;
	
	public ServerMessager(Server serverIn) {
		clueServer = serverIn;
		startTime = System.currentTimeMillis();
	}

	public ServerMessager() {
	}

	Thread newClientThread = null;

	/**
	 * After hearing from a new client, establishes a new socket for them
	 * and kicks off a thread that constantly listens over that socket.
	 * 
	 * Also adds the new socket to the playerToSocket map, so that data 
	 * can be sent through it.
	 * 
	 * Waits only max time to join, then ends
	 */
	public List<Player> establishClientConnections() {
		ServerSocket serverSocket = null;

		// create a socket & listener for each player that wants to join
		try {
			try {
				serverSocket = new ServerSocket(9999);
			} catch (Exception e) {
				System.out.println("Error creating new ServerSocket: " + e);
				e.printStackTrace();
			}
			// start counting time once the server socket is established
			long time = System.currentTimeMillis() - startTime;

			
			// continue accepting data
			while (playerId < 3
					|| (time < MAX_TIME_TO_JOIN && !(playerId >= 6))) {
				// thread should accept new clients and call back to Server Messager
				
				if (newClientThread == null) 
				{
					newClientThread = new ClientAdder(serverSocket, this);
					newClientThread.start();
				}
				
				time = System.currentTimeMillis() - startTime;
				Thread.sleep(1L);
			}
			
			// done establishing clients; close server socket
			serverSocket.close();

			newClientThread.interrupt();
			
			if (newClientThread!=null) newClientThread.interrupt();
			

		} catch (IOException e) {
			System.err.println("Could not listen on port: 4444.");
			e.printStackTrace();
			System.exit(-1);
		} // now all the threads have been created
		catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return players;
	}

	private void createNewPlayer(Socket newSocket) {
		Player currPlayer = null;
		try {
			// create a new thread that listens over socket for new
			// player
			currPlayer = new Player(playerId++);
			players.add(currPlayer);
		} catch (Exception e) {
			System.out
			.println("Error creating new thread that listens over socket for new player: "
					+ e);
			e.printStackTrace();
		}
		Socket currSocket = null;
		ObjectOutputStream outputStreamForPlayer = null;
		try {
			// get new client's socket
			outputStreamForPlayer = new ObjectOutputStream(
					newSocket.getOutputStream());
		} catch (Exception e) {
			System.out.println("Error getting new client's socket: "
					+ e);
			e.printStackTrace();
		}
		try {
			// start listening over socket
			ObjectInputStream oi = new ObjectInputStream(
					newSocket.getInputStream());
			new DataListener(this, oi).start();
		} catch (Exception e) {
			System.out.println("Error listening over socket: " + e);
			e.printStackTrace();
		}
		try {
			// save the player's output stream for this socket
			playerToSocketOutput.put(currPlayer.playerId, outputStreamForPlayer);
		} catch (Exception e) {
			System.out
					.println("Error saving the player's output stream for this socket: "
							+ e);
			e.printStackTrace();
		}
		newClientThread = null;
	}

	public class ClientAdder extends Thread
	{
		private ServerSocket serverSocket;
		private ServerMessager messager;

		public ClientAdder(ServerSocket serverSocketIn, ServerMessager messagerIn) {
			serverSocket = serverSocketIn;
			messager = messagerIn;
		}

		public void run() 
		{
			try {
				Socket newSocket = serverSocket.accept();
				messager.createNewPlayer(newSocket);
			}
			catch (SocketException e)
			{
			;	
			}
			catch (IOException e) {
				e.printStackTrace();
			}
		};
		
	}
	
	Socket createSocketForPlayer(ServerSocket serverSocket) throws IOException  
	{
		Socket newSocket = serverSocket.accept();
		return newSocket;
	}

	public void receiveData(NetworkDataWrapper data) 
	{
		clueServer.receiveDataFromClient(data);
	}
	

	/**
	 * Sends data to specified player only
	 * @param dataIn
	 * @param player
	 */
	public void sendData(NetworkDataWrapper dataIn,
			Player player) 
	{
		try {
			playerToSocketOutput.get(player.playerId).writeObject(dataIn);
		} catch (IOException e) {
			System.err.append("Couldn't write data to output stream for player: "+player.playerId);
			e.printStackTrace();
		}
	}

	/**
	 * Sends data to all the players
	 * @param dataIn
	 */
	public void sendDataToAll(NetworkDataWrapper dataIn) 
	{
		for (Player currPlayer : players)
		{
			clueServer.out("sending data to player: "+currPlayer.character.getName());
			clueServer.out(dataIn.toString());
			sendData(dataIn, currPlayer);
			clueServer.out("data sent to player: "+currPlayer.character.getName());
		}
	}

}// end ServerMessageLayer